package jp.chiheisen.httpserveranywhere;

import java.awt.AWTException;
import java.awt.CheckboxMenuItem;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.BindException;
import java.net.InetAddress;

import javax.activation.FileTypeMap;
import javax.activation.MimetypesFileTypeMap;
import javax.imageio.ImageIO;

import jp.chiheisen.common.CloseUtils;
import jp.chiheisen.common.ResourceInputStream;
import jp.chiheisen.common.dialog.Dialog;
import jp.chiheisen.common.dialog.Validator;
import jp.chiheisen.common.dialog.Validators;
import jp.chiheisen.httpserver.Server;

/**
 * HTTP Server Anywhere のメインクラスです。
 * 
 * @author YujiYamamoto
 * @since 1.0
 */
public class HttpServerAnywhere {

	/** プログラム名 */
	private static final String PROGRAM_NAME = "HTTP Server Anywhere";
	/** アイコンのパス */
	private static final String ICON_PATH = "META-INF/trayicon.png";
	
	/**
	 * HTTPサーバを起動します。
	 * 
	 * @param args[0] port ポート番号(省略可)
	 * @throws IOException サーバの起動に失敗したとき、リソースの読み込みに失敗したとき  
	 */
	public static void main(String[] args) throws IOException{
		int port = args.length < 1 ? 80 : Integer.parseInt(args[0]);
		
		init();
		
		HttpServerAnywhere server =  new HttpServerAnywhere(port);
		server.displayHostAddress();
	}
	
	private static void init() throws FileNotFoundException{
		FileTypeMap fileTypeMap = new MimetypesFileTypeMap(new ResourceInputStream("META-INF/mime.types"));
		FileTypeMap.setDefaultFileTypeMap(fileTypeMap);
	}

	/** 待ち受けホスト */
	private Host host;
	/** ポート番号 */
	private int port;
	/** 起動中のサーバ */
	private Server server;
	/** トレイアイコン */
	private TrayIcon icon;
	
	public HttpServerAnywhere(int defaultPort) throws IOException{
		this.host = Host.LOOPBACK;
		this.port = defaultPort;
		start();
		
		if(SystemTray.isSupported()){
			InputStream input = null;
			try{
				input = new ResourceInputStream(ICON_PATH);
				Image image = ImageIO.read(input);
				
				TrayIcon icon = new TrayIcon(image, PROGRAM_NAME);
		        icon.setPopupMenu(createMenu());
				SystemTray.getSystemTray().add(icon);
				this.icon = icon;
			} catch (AWTException e) {
	        	// デスクトップのシステムトレイが見つからない -> あきらめて、コンソールに表示
	        }finally{
	        	CloseUtils.safeClose(input);
	        }
		}
	}
	
	private void start() throws IOException{
		Server server;
		while(true){
			try{
				server = new Server(host.createAddress(port));
				break;
			}catch(BindException e){
				Dialog dialog = new Dialog(PROGRAM_NAME);
				String message = String.format("すでに%s番ポートは使用されています。\nほかのポート番号を指定してください。", port);
				String retryMessage = "ポート番号を指定してください。\n\n有効なポート番号は 0 から 65535 の範囲です。\nポート番号が 0 の場合は、適当なポートが選択されます。";
				Validator validator = Validators.createRangeValidator(0, 65535);
				
				String input = dialog.input(message, retryMessage, validator);
				if(input == null){
					System.exit(0);
				}
				port = Integer.parseInt(input);
			}
		}
		
		this.server = server;
		this.server.start();
	}
	
	private void restart() throws IOException{
		if(this.server != null){
			this.server.stop();
		}
		this.server = null;
		
		start();
	}
	
	public void displayHostAddress(){
		try{
			StringBuilder url = new StringBuilder();
			for(InetAddress address : host.getAddress()){
				url.append("http://" + address.getHostAddress() + (port == 80 ? "" : ":" + port) + "/");
				url.append("\r\n");
			}
			if(url.length() != 0){
				url.deleteCharAt(url.length() - 1).deleteCharAt(url.length() - 1);
			}
			
			displayMessage(url.toString(), TrayIcon.MessageType.INFO);
		}catch(IOException e){
			displayMessage("IPアドレスを取得できませんでした。", TrayIcon.MessageType.ERROR);
		}
	}

	private void displayMessage(String message, TrayIcon.MessageType meesageType) {
		if(icon != null){
			this.icon.displayMessage(icon.getToolTip(), message, meesageType);
		}else{
			System.out.println(String.format("[%s]%s", meesageType, message));
		}
	}

	private PopupMenu createMenu() {
        CheckboxMenuItem onGlobal = new CheckboxMenuItem("他のPCからのアクセスを許可する。", false);
        onGlobal.addItemListener(new ItemListener(){
			@Override
			public void itemStateChanged(ItemEvent event) {
				switch(event.getStateChange()){
				case ItemEvent.SELECTED:
					host = Host.WILD;
					break;
				case ItemEvent.DESELECTED:
					host = Host.LOOPBACK;
					break;
				default:
					throw new Error("Unknown state. [param=" + event.paramString() + "]");
				}
				
				try{
					restart();
					displayHostAddress();
				}catch(IOException e){
					// 変更失敗
					CheckboxMenuItem item = ((CheckboxMenuItem)event.getSource());
					item.setState(!item.getState());
					return;
				}
			}
		});
        MenuItem separator = new MenuItem("-");
        MenuItem exitItem = new MenuItem("終了");
        exitItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.exit(0);
            }
        });

		PopupMenu menu = new PopupMenu();
        menu.add(onGlobal);
        menu.add(separator);
        menu.add(exitItem);
        
		return menu;
	}
}
